<?php
declare (strict_types=1);

namespace app\middleware;

use app\user\model\User;
use think\facade\Request;

class ApiSignVerify
{
    //免校验token方法
    public $is_login = [];
    //免校验签名sign方法
    public $is_check = [];
    //接口校验密钥
    public $apiSerect;// 'kajsbkd123b55kwbdkj128y87';
    // 是否开启接口验证 默认是开启的
    public $open = true;
    public $mid;
    public $field = 'id,avatarurl,nickname as nickName,phone,binding_time,invite_id,invite_code,openid,is_fictitious,is_proceeds,level,remarks,last_time,add_time,openid,session_key';

    public function __construct()
    {
        // 从配置文件中读取
        $this->is_login = config('apiVerify.is_login');
        $this->is_check = config('apiVerify.is_check');
        $this->apiSerect = config('apiVerify.apiSerect');
        $this->open = config('apiVerify.open');
    }

    /**
     * 处理请求
     *
     * @param \think\Request $request
     * @param \Closure $next
     * @return Response
     */
    public function handle($request, \Closure $next)
    {
        $url = $request->server('REQUEST_URI');
        $uri = explode('/', $url);
        $model = $uri[1] ?? '';
        $controller = $uri[2] ?? '';
        $action = explode('?', $uri[3] ?? '');
        if (empty($model) || empty($controller) || empty($action) || !isset($action[0])) return retu_json(400, '请求地址有误！');
        if (!in_array("{$model}/{$controller}/{$action[0]}", $this->is_check)) {
//        if(!in_array(Request::param('path_url',''),$this->is_check)){ #校验接口签名
            $bool = $this->checkSign();
            if ($bool !== true)
                return retu_json(400, $bool['msg'] ?? '签名校验失败！', $bool);
        }

        if (!in_array("{$model}/{$controller}/{$action[0]}", $this->is_login)) { #校验token并获取用户信息
            $token = input('server.HTTP_AUTHORIZATION');
            if (empty($token)) return retu_json(400, 'token不能够为空！');
            $bool = $this->checkToken($token);
            if (!empty($this->mid) && is_numeric($this->mid)) {
                $user = (new User())->field($this->field)->where(['id' => $this->mid, 'is_delete' => 0])->find();
                if (empty($user)) return retu_json(400, '用户信息异常，请联系客服！');
                $user->nickname = $user->nickName;
                bind('user', $user);
            } else return retu_json(400, $bool['msg'] ?? 'token失效或者不存在！');
        }
        return $next($request);
    }

    /**
     * @desc 校验token的有效性
     */
    public function checkToken($token)
    {
        $redis = getRedis();
        $key = "token:" . $token;
        $mid = $redis->get($key);
        if (!$mid) {
            return ['msg' => 'token已过期或不合法，请重新登录！'];
        }
        $this->mid = $mid;
        return true;
    }

    /**
     * @desc 校验签名
     */
    private function checkSign()
    {
        // 如果是关闭了 接口验证
        if (!$this->open) {
            return true;
        }

        $sign = Request::param('sign');
        if (empty($sign)) {
            return ['msg' => 'sign为空！'];
        }
        $timestamp = Request::param('timestamp');
        if ($timestamp < time() - 120) {
            return ['msg' => '签名已超时！'];
        }
        $postParams = Request::except(['path_url']);
        //$postParams = input('post.');
        // 删除掉 签名进行拼接
        if (empty($postParams)) {
            $postParams['timestamp'] = $timestamp;
        } else unset($postParams['sign']);
        $test_sign = $postParams['test_sign'] ?? '';
        unset($postParams['test_sign']);

        $params = [];
        ksort($postParams);
        // 迭代进行拼接字符串
        foreach ($postParams as $k => $v)
            $params[] = sprintf("%s%s", $k, $v);
        $apiSerect = $this->apiSerect;
        // 进行拼接字符串
        $str = sprintf("%s%s%s", $apiSerect, implode('', $params), $apiSerect);
        if (md5($str) != $sign) {
            return APP_DEBUG ? ['msg' => '签名校验失败！', 'sign_str' => $str, 'sign' => md5($str), 'api_sign' => $sign, 'api_test_str' => $test_sign] : ['msg' => '签名校验失败！'];
        } else return true;
    }

    /**
     * @desc nonce校验预防接口重放
     */
    private function checkNonce()
    {
        $sign = input('post.sign');
        if (empty($sign)) {
            return ['msg' => 'sign为空！'];
        }
        $nonce = input('post.nonce');
        if (empty($nonce)) {
            return ['msg' => 'nonce为空！'];
        }
        $nonceKey = sprintf("sign:%s:nonce:%s", $sign, $nonce);
        $redis = getRedis();
        $nonV = $redis->get($nonceKey);
        if (!empty($nonV)) {
            return ['msg' => '请勿重复调用！'];
        } else {
            $redis->set($nonceKey, $nonce, 60);
            return true;
        }
    }
}
